package ufrj.safcp.tools.qrcode;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;

import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.zxing.qrcode.QRCodeWriter;

/**
 * 
 * Classe que compacta e descompacta as informações do QRCode usando bzip2 ou gz, conforme configurado.
 * 
 * @author Bruno Medeiros
 *
 */
public class GenericCompressorQRCodeDataDecorator implements IQRCodeHandler {
	
	private static Logger logger = LoggerFactory.getLogger(GenericCompressorQRCodeDataDecorator.class);

	private IQRCodeHandler decorated;

	private String format;

	public GenericCompressorQRCodeDataDecorator(IQRCodeHandler decorated, String format) {
		super();
		this.decorated = decorated;
		this.format = format;
	}
	
	@Override
	public BufferedImage encode(byte[] contents) {
		
		if (logger.isDebugEnabled()) {
			StringBuffer sb = new StringBuffer();
			sb.append("{");
			boolean first = true;
			for (byte b : contents) {
				if (first) {
					first = false;
				} else {
					sb.append(", ");
				}
				sb.append(b);
			}
			sb.append("}");
			logger.debug("Compressing byte[] " + sb);
		}
		QRCodeWriter codeWriter = new QRCodeWriter();
		
		
		Deflater compressor = new Deflater();
		compressor.setLevel(Deflater.BEST_COMPRESSION);
		compressor.setStrategy(Deflater.FILTERED);
		compressor.setInput(contents);
		compressor.finish();
		

		ByteArrayOutputStream bos;
		try {
			bos = new ByteArrayOutputStream(contents.length);
			CompressorOutputStream os = new CompressorStreamFactory().createCompressorOutputStream(this.format, bos);
			os.write(contents);
			os.close();
		    bos.close();
		} catch (IOException e) {
			throw new IllegalStateException("Error compressing to bzip format.", e);
		} catch (CompressorException e) {
			throw new IllegalStateException("Error getting compressor.", e);
		}

		// Get the compressed data
		byte[] compressedData = bos.toByteArray();
		
		if (logger.isDebugEnabled()) {
			logger.debug("Compressed byte[] of " + contents.length + " bytes using " + this.format + ", compress ratio  = "
					+ (compressedData.length * 1.0 / contents.length));
		}
		return this.decorated.encode(compressedData);
	}

	@Override
	public byte[] decode(Image contents) throws UnredableCodeException {
		byte[] decoded = this.decorated.decode(contents);
		
		
		ByteArrayInputStream bis;
		ByteArrayOutputStream bos;
		try {
			bis = new ByteArrayInputStream(decoded);
			CompressorInputStream is = new CompressorStreamFactory().createCompressorInputStream(this.format, bis);
			byte[] buff = new byte[1024];
			
			bos = new ByteArrayOutputStream();
			
			int a;
			while ((a = is.read(buff)) > 0) {
				bos.write(buff, 0, a);
			}
			is.close();
		    bis.close();
		} catch (IOException e) {
			throw new IllegalStateException("Error decompressing from bzip format.", e);
		} catch (CompressorException e) {
			throw new IllegalStateException("Error getting decompresor.", e);
		}

		return bos.toByteArray();
	}

}
